﻿using System;
using System.Collections.Generic;
using System.IO;
using Import_glTF.gltf;
using Import_glTF.Gltf;
using Newtonsoft.Json;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;

namespace Import_glTF
{
    internal enum LoadStatus
    {
        Success,
        Failure
    }
    internal class LoadResult
    {
        public LoadStatus Status { get; set; }
        public string Message { get; set; }
        public LoadResult(LoadStatus status, string message)
        {
            Status = status;
            Message = message;
        }
    }
    internal class GltfModelLoader
    {
        private readonly string rootFilePath;
        private readonly string rootFolderPath;
        private readonly RhinoDoc doc;
        private Gltf.Gltf gltf;
        private List<BufferReader> bufferReaders;
        private Dictionary<int, int> meshIndices;
        private Stack<Transform> transformStack;
        public GltfModelLoader(string strFile, RhinoDoc doc)
        {
            rootFilePath = strFile;
            rootFolderPath = Path.GetDirectoryName(rootFilePath);
            this.doc = doc;
        }
        public LoadResult Load()
        {
            if (rootFilePath == null || !File.Exists(rootFilePath))
            {
                return new LoadResult(LoadStatus.Failure, $"【{rootFilePath}】文件不存在");
            }
            try
            {
                string json = File.ReadAllText(rootFilePath);
                gltf = JsonConvert.DeserializeObject<Gltf.Gltf>(json);
            }
            catch (Exception ex)
            {
                return new LoadResult(LoadStatus.Failure, $"读取文件【{rootFilePath}】失败:{ex.Message}");
            }
            bufferReaders = new List<BufferReader>();
            foreach (Gltf.GltfBuffer buffer in gltf.buffers)
            {
                string bufferFile = Path.Combine(rootFolderPath, buffer.Uri);
                bufferReaders.Add(new BufferReader(bufferFile));
            }

            ReadMeshs();
            AddNodes();
            foreach (BufferReader bufferReader in bufferReaders)
            {
                bufferReader.Close();
            }
            return new LoadResult(LoadStatus.Success, "加载完成！");
        }
        private void AddNodes()
        {
            transformStack = new Stack<Transform>();
            transformStack.Push(Transform.Scale(Point3d.Origin, 1000));
            GltfNode currentNode = gltf.nodes[0];
            AddNode(currentNode);
        }
        private void AddNode(GltfNode node)
        {
            Transform nodeTransform = GetNodeTransform(node);
            Transform currentTransform = transformStack.Peek() * nodeTransform;
            string nodeName = GetNodeName(node);
            Group group = doc.Groups.FindName(nodeName);
            int groupIndex = group != null ? group.Index : doc.Groups.Add(nodeName);
            if (node.Mesh != null)
            {
                int instanceIndex = meshIndices[node.Mesh.Value];
                Guid guid = doc.Objects.AddInstanceObject(instanceIndex, currentTransform);
                //Guid guid = doc.Objects.AddInstanceObject(instanceIndex, Transform.Identity);
                doc.Groups.AddToGroup(groupIndex, guid);
            }
            transformStack.Push(currentTransform);
            if (node.Children != null)
            {
                foreach (int childIndex in node.Children)
                {
                    GltfNode child = gltf.nodes[childIndex];
                    AddNode(child);
                }
            }
            transformStack.Pop();
        }
        private void ReadMeshs()
        {
            meshIndices = new Dictionary<int, int>();
            for (int i = 0; i < gltf.meshes.Count; i++)
            {
                string meshName = $"{gltf.meshes[i].Name}_{i}";
                List<Mesh> meshes = ReadMeshGroup(gltf.meshes[i]);
                int instanceIndex = doc.InstanceDefinitions.Add(meshName, meshName, Point3d.Origin, meshes);
                meshIndices.Add(i, instanceIndex);
            }
        }
        private List<Mesh> ReadMeshGroup(Gltf.GltfMesh mesh)
        {
            List<Mesh> meshes = new List<Mesh>();
            foreach (Gltf.GltfMeshPrimitive primitive in mesh.Primitives)
                meshes.Add(ReadMesh(primitive));
            return meshes;
        }
        private Mesh ReadMesh(Gltf.GltfMeshPrimitive primitive)
        {
            Mesh mesh = new Mesh();
            mesh.Vertices.AddVertices(ReadMeshPositions(primitive.Attributes.POSITION));
            if (primitive.Attributes.NORMAL.HasValue)
                mesh.Normals.AddRange(ReadMeshNormals(primitive.Attributes.NORMAL.Value));
            if (primitive.Attributes.TEXCOORD_0.HasValue)
                mesh.TextureCoordinates.AddRange(ReadTexcoords(primitive.Attributes.TEXCOORD_0.Value));
            uint[] indices = ReadIndices(primitive.Indices);
            for (int i = 0; i < indices.Length; i += 3)
                mesh.Faces.AddFace((int)indices[i], (int)indices[i + 1], (int)indices[i + 2]);
            mesh.Compact();
            return mesh;
        }
        private uint[] ReadIndices(int indicesAccessorIndex)
        {
            GltfAccessor accessor = gltf.accessors[indicesAccessorIndex];
            BufferReader bufferReader = GetBufferReader(accessor);
            GltfBufferView bufferView = gltf.bufferViews[accessor.BufferView];
            return bufferReader.ReadIndices(accessor, bufferView);
        }
        private Point2f[] ReadTexcoords(int texcoordAccessorIndex)
        {
            GltfAccessor accessor = gltf.accessors[texcoordAccessorIndex];
            BufferReader bufferReader = GetBufferReader(accessor);
            GltfBufferView bufferView = gltf.bufferViews[accessor.BufferView];
            return bufferReader.ReadTexcoords(accessor, bufferView);
        }
        private Vector3f[] ReadMeshNormals(int normalAccessorIndex)
        {
            GltfAccessor accessor = gltf.accessors[normalAccessorIndex];
            BufferReader bufferReader = GetBufferReader(accessor);
            GltfBufferView bufferView = gltf.bufferViews[accessor.BufferView];
            return bufferReader.ReadNormals(accessor, bufferView);
        }
        private Point3d[] ReadMeshPositions(int positionAccessorIndex)
        {
            GltfAccessor accessor = gltf.accessors[positionAccessorIndex];
            BufferReader bufferReader = GetBufferReader(accessor);
            GltfBufferView bufferView = gltf.bufferViews[accessor.BufferView];
            return bufferReader.ReadPositions(accessor, bufferView);
        }
        private BufferReader GetBufferReader(Gltf.GltfAccessor accessor)
        {
            int bufferViewIndex = accessor.BufferView;
            int bufferIndex = gltf.bufferViews[bufferViewIndex].Buffer;
            return bufferReaders[bufferIndex];
        }

        private int GetNodeIndex(GltfNode node)
        {
            return gltf.nodes.IndexOf(node);
        }
        private List<GltfNode> GetNodePath(Gltf.GltfNode node)
        {
            List<GltfNode> nodes = new List<GltfNode>();
            GltfNode root = gltf.nodes[0];
            DFS(root, node, nodes);
            return nodes;
        }
        private bool DFS(GltfNode node, GltfNode target, List<GltfNode> path)
        {
            if (node == null) return false;
            path.Add(node);
            if (node == target) return true;
            foreach (int childIndex in node.Children)
            {
                GltfNode child = gltf.nodes[childIndex];
                if (DFS(child, target, path)) return true;
            }
            path.RemoveAt(path.Count - 1);
            return false;
        }
        private static Transform GetNodeTransform(GltfNode node)
        {
            ///In Rhino Transform is
            /// M00 M01 M02 M03
            /// M10 M11 M12 M13
            /// M20 M21 M22 M23
            /// M30 M31 M32 M33
            /// Point3d.Transform(xform)is P=M*p，点是列向量
            /// double x = (xform.m_00 * m_x + xform.m_01 * m_y + xform.m_02 * m_z + xform.m_03);
            /// double y = (xform.m_10 * m_x + xform.m_11 * m_y + xform.m_12 * m_z + xform.m_13);
            /// double z = (xform.m_20 * m_x + xform.m_21 * m_y + xform.m_22 * m_z + xform.m_23);
            /// 
            /// In glTF Matrix is 
            /// M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11,M12,M13,M14,M15
            /// Matrix.OfPoint(pt)is P=p*M,点是行向量
            /// double x=M0*m_x+M4*m_y+M8*m_z+M12;
            /// double y=M1*m_x+M5*m_y+M9*m_z+M13;
            /// double z=M2*m_x+M6*m_y+M10*m_z+M14;
            Transform xfrom = Transform.Identity;
            if (node.Matrix != null)
            {
                xfrom.M00 = node.Matrix[0];
                xfrom.M10 = node.Matrix[1];
                xfrom.M20 = node.Matrix[2];
                xfrom.M30 = node.Matrix[3];
                xfrom.M01 = node.Matrix[4];
                xfrom.M11 = node.Matrix[5];
                xfrom.M21 = node.Matrix[6];
                xfrom.M31 = node.Matrix[7];
                xfrom.M02 = node.Matrix[8];
                xfrom.M12 = node.Matrix[9];
                xfrom.M22 = node.Matrix[10];
                xfrom.M32 = node.Matrix[11];
                xfrom.M03 = node.Matrix[12];
                xfrom.M13 = node.Matrix[13];
                xfrom.M23 = node.Matrix[14];
                xfrom.M33 = node.Matrix[15];
                return xfrom;
            }
            else
            {
                Transform translation = node.Translation != null ?
                                   Transform.Translation(node.Translation[0], node.Translation[1], node.Translation[2]) : Transform.Identity;

                Transform rotation = Transform.Identity;
                if (node.Rotation != null && node.Name!= "xFormNode")
                {
                    //Rhino中Quaternion为[A,B,C,D],glTF里面为[x,y,z,w]
                    Quaternion quaternion =
                   new Quaternion
                   {
                       A = node.Rotation[3],
                       B = node.Rotation[0],
                       C = node.Rotation[1],
                       D = node.Rotation[2]
                   };
                    quaternion.GetRotation(out rotation);
                }
                Transform scale = Transform.Identity;
                if (node.Scale != null)
                {
                    scale = Transform.Scale(Plane.WorldXY, node.Scale[0], node.Scale[1], node.Scale[2]);
                }
                return translation * rotation * scale;
            }
        }
        private static string GetNodeName(Gltf.GltfNode node)
        {
            string propertyName = "elementId";
            string nodeName = node.Name;
            if (node.Extensions != null)
            {
                GltfExtensions extensions = node.Extensions;
                ExtStructuralMetadata metadata = extensions.EXT_structural_metadata;
                if (metadata != null)
                {
                    if (metadata.HasProperty(propertyName))
                    {
                        nodeName += metadata.properties[propertyName];
                    }
                }
            }
            return nodeName;
        }
        private static bool IsLeafNode(Gltf.GltfNode node)
        {
            return node.Children == null || node.Children.Count == 0;
        }
        private static string PrintTransform(Transform transform)
        {
            return $"({transform.M03},{transform.M13},{transform.M23})";
        }
    }
}
